/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Trace.h"
#include <xml/Stream.h>
#include <math/utl.h>

namespace liba {
namespace lib3d {
namespace node {

const Atom Trace::node_name("Trace");
const Atom Trace::shader_name("Shader");

Trace::Trace( const Atom & name, Node * parent )
:	Node( name, parent )
{
	node_type = node_name;
	hard::Manager::add_dependent( this );
}

Trace::~Trace()
{
	hard::Manager::remove_dependent( this );
}

void Trace::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back( SweetPair(ha, ha->create_vertex_buffer()) );
}

int Trace::update_buffer(hard::VertexBuffer * vb)
{
	double position = 0;
	double delta = 0.1;

	const Vector tolerance = Vector(5, 5, 5);

	std::vector<Vector> path;
	std::vector<Vector> grads;

	Vector old_val = envelope_tcb.evaluate(0);
	path.push_back(old_val);
	Vector grad = envelope_tcb.evaluate_gradient(0);
	grad.normalize();
	grads.push_back(grad);

	//TODO calc gradient
	while(position != 1)
	{
		double new_position = position + delta;
		if(new_position > 1)
			new_position = 1;
		Vector val = envelope_tcb.evaluate(new_position);
		Vector delta_val = val - old_val;
		delta_val.x = math::abs(delta_val.x);
		delta_val.y = math::abs(delta_val.y);
		delta_val.z = math::abs(delta_val.z);
        if(delta_val.x > tolerance.x || delta_val.y > tolerance.y || delta_val.z > tolerance.z)
		{
			delta /= 2;
			continue;
		}
		else
		{
			delta *= 2;
		}

		old_val = val;
		position = new_position;

		path.push_back(val);

		Vector grad = envelope_tcb.evaluate_gradient(new_position);
		grad.normalize();
		grads.push_back(grad);
	}

	SimpleArray<float> positions((path.size()-1)*3*3*2);
	SimpleArray<float> colors((path.size()-1)*4*3*2);
	SimpleArray<float> uvs((path.size()-1)*2*3*2);

	float * pos_data = positions.data();
	float * col_data = colors.data();
	float * uv_data = uvs.data();

	float alpha = 0.5;

	double len = 0;
	for(size_t i = 1; i < path.size(); ++i)
	{
		Vector start = path[i-1];
		Vector end = path[i];

		double new_len = len + (end-start).length()/200;

		Color color(1, 1, 1);
		double s = 32;

		Vector grad_tmp = grads[i-1];
		std::swap(grad_tmp.x, grad_tmp.y);
		Vector prev_delta = Vector(-grad_tmp.x*s, grad_tmp.y*s,0);

		grad_tmp = grads[i];
		std::swap(grad_tmp.x, grad_tmp.y);
		Vector delta = Vector(-grad_tmp.x*s, grad_tmp.y*s,0);

		Vector position = start + prev_delta;
		*pos_data++ = position.x;
		*pos_data++ = position.y;
		*pos_data++ = position.z;

		position = end - delta;
		*pos_data++ = position.x;
		*pos_data++ = position.y;
		*pos_data++ = position.z;

		position = start - prev_delta;
		*pos_data++ = position.x;
		*pos_data++ = position.y;
		*pos_data++ = position.z;

		position = start + prev_delta;
		*pos_data++ = position.x;
		*pos_data++ = position.y;
		*pos_data++ = position.z;

		position = end + delta;
		*pos_data++ = position.x;
		*pos_data++ = position.y;
		*pos_data++ = position.z;

		position = end - delta;
		*pos_data++ = position.x;
		*pos_data++ = position.y;
		*pos_data++ = position.z;

		*col_data++ = color.r;
		*col_data++ = color.g;
		*col_data++ = color.b;
		*col_data++ = float(alpha);

		*col_data++ = color.r;
		*col_data++ = color.g;
		*col_data++ = color.b;
		*col_data++ = float(alpha);

		*col_data++ = color.r;
		*col_data++ = color.g;
		*col_data++ = color.b;
		*col_data++ = float(alpha);

		*col_data++ = color.r;
		*col_data++ = color.g;
		*col_data++ = color.b;
		*col_data++ = float(alpha);

		*col_data++ = color.r;
		*col_data++ = color.g;
		*col_data++ = color.b;
		*col_data++ = float(alpha);

		*col_data++ = color.r;
		*col_data++ = color.g;
		*col_data++ = color.b;
		*col_data++ = float(alpha);

		*uv_data++ = len;
		*uv_data++ = 1;

		*uv_data++ = new_len;
		*uv_data++ = 0;

		*uv_data++ = len;
		*uv_data++ = 0;

		*uv_data++ = len;
		*uv_data++ = 1;

		*uv_data++ = new_len;
		*uv_data++ = 1;

		*uv_data++ = new_len;
		*uv_data++ = 0;

		len = new_len;
	}

	SimpleArray< float > empty_float;
	HardVector::iterator it = hard_vector.begin();
	for( ;it != hard_vector.end(); ++it )
	{
		if( !it->second->update(
			positions, empty_float, colors, uvs, empty_float ) )
		{
			throw Gluck(__FUNCTION__, "it->second->update() failed");
		}
	}

	return (path.size()-1)*2;
}

void Trace::RenderToList( hard::Hardware * hardware, hard::RenderList * render_list )
{
	hard::VertexBuffer * vb = find_data(hardware);

	hard::RenderModel render_model;
	render_model.world = GetGlobalMatrix();

	//TODO: do not update each frame, test version only
	int count = update_buffer(vb);

	render_model.primitive_start = 0;
	render_model.primitive_count = count;
	render_model.vertex_start = 0;
	render_model.vertex_count = 0;
	shader.render_to_list( hardware, &render_model.material );
	render_model.vb = vb;
	render_list->models.push_back(render_model);

	return Node::RenderToList( hardware, render_list );
}

animate::Node * Trace::FindAnimateNode( const Atom & name, bool recursion )
{
	if( name == shader.GetName() )
		return &shader;

	return Node::FindAnimateNode( name, recursion );
}

bool Trace::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	return Node::on_attribute( prov, name, value );
}

bool Trace::on_node( xml::Provider * prov, const std::string & name )
{
	if( name == "Shader" )
		return prov->parse_object( &shader );

	if( name == "EnvelopeTCB" )
	{
		return prov->parse_object( &envelope_tcb );
	}

	return Node::on_node( prov, name );
}

bool Trace::on_end_node( xml::Provider * prov )
{
	return Node::on_end_node( prov );
}

void Trace::LifeCycle(time_float delta_time)
{}

} // namespace node
} // namespace lib3d
} // namespace liba

